/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Reg } from './reg';
import { doubleCondCode, FP, GP, nonZero, positiveOrZero, PTR, relCondCode, resCondCode, signed, zero, dDouble, nFloat, nInt32, nInt64 } from './symbols';
import { Tmp } from './tmp';
import { isRepresentableAsInt32, symbolName } from './util';
import { StackSlot } from './stack_solt';

const INT32_MAX: number = 2147483647;
const UINT32_MAX: number = 4294967295;
const INT32_MIN: number = -2147483648;

const ARG_BYTES_NUM_2: number = 2;
const ARG_BYTES_NUM_3: number = 3;
const ARG_BYTES_NUM_4: number = 4;
const ARG_BYTES_NUM_5: number = 5;
const ARG_BYTES_NUM_6: number = 6;
const ARG_BYTES_NUM_7: number = 7;
const ARG_BYTES_NUM_8: number = 8;
const ARG_BYTES_NUM_9: number = 9;
const ARG_BYTES_NUM_10: number = 10;
const ARG_BYTES_NUM_11: number = 11;
const ARG_BYTES_NUM_12: number = 12;
const ARG_BYTES_NUM_13: number = 13;
const ARG_BYTES_NUM_14: number = 14;
const ARG_BYTES_NUM_16: number = 16;
const ARG_BYTES_NUM_32: number = 32;
const ARG_BYTES_NUM_64: number = 64;

export class Arg {
  // Arg kinds
  static invalid: string = 'Invalid';
  static tmp: string = 'Tmp';
  static imm: string = 'Imm';
  static bigImm: string = 'BigImm';
  static bitImm: string = 'BitImm';
  static bitImm64: string = 'BitImm64';
  static addr: string = 'Addr';
  static stack: string = 'Stack';
  static callArg: string = 'CallArg';
  static index: string = 'Index';
  static relCond: string = 'RelCond';
  static resCond: string = 'ResCond';
  static doubleCond: string = 'DoubleCond';
  static special: string = 'Special';
  static width: string = 'Width';
  static widthArg: string = 'WidthArg';

  // Arg roles
  static use: string = 'Use';
  static coldUse: string = 'ColdUse';
  static lateUse: string = 'LateUse';
  static lateColdUse: string = 'LateColdUse';
  static def: string = 'Def';
  static zDef = 'ZDef';
  static useDef: string = 'UseDef';
  static useZDef: string = 'UseZDef';
  static earlyDef: string = 'EarlyDef';
  static scratch: string = 'Scratch';
  static useAddr: string = 'UseAddr';
  _kind: string;
  _tmp: Reg | null;
  _value: number;
  _lowValue: number;
  _highValue: number;
  _base: Reg | null;
  _offset: number;
  _slot: StackSlot;
  _index: Reg | null;
  _scale: number;
  _condition: string;
  _width: number;

  constructor() {
    this._kind = Arg.invalid;
  }

  isAnyUse(role: string): boolean {
    switch (role) {
      case Arg.use:
      case Arg.coldUse:
      case Arg.useDef:
      case Arg.useZDef:
      case Arg.lateUse:
      case Arg.lateColdUse:
      case Arg.scratch:
        return true;
      case Arg.def:
      case Arg.zDef:
      case Arg.useAddr:
      case Arg.earlyDef:
        return false;
      default:
        throw new Error('Bad role');
    }
  }

  isColdUse(role: string): boolean {
    switch (role) {
      case Arg.coldUse:
      case Arg.lateColdUse:
        return true;
      case Arg.use:
      case Arg.useDef:
      case Arg.useZDef:
      case Arg.lateUse:
      case Arg.def:
      case Arg.zDef:
      case Arg.useAddr:
      case Arg.scratch:
      case Arg.earlyDef:
        return false;
      default:
        throw new Error('Bad role');
    }
  }

  isWarmUse(role: string): boolean {
    return this.isAnyUse(role) && !this.isColdUse(role);
  }

  cooled(role: string): string {
    switch (role) {
      case Arg.coldUse:
      case Arg.lateColdUse:
      case Arg.useDef:
      case Arg.useZDef:
      case Arg.def:
      case Arg.zDef:
      case Arg.useAddr:
      case Arg.scratch:
      case Arg.earlyDef:
        return role;
      case Arg.use:
        return Arg.coldUse;
      case Arg.lateUse:
        return Arg.lateColdUse;
      default:
        throw new Error('Bad role');
    }
  }

  static isEarlyUse(role: string): boolean {
    switch (role) {
      case Arg.use:
      case Arg.coldUse:
      case Arg.useDef:
      case Arg.useZDef:
        return true;
      case Arg.def:
      case Arg.zDef:
      case Arg.useAddr:
      case Arg.lateUse:
      case Arg.lateColdUse:
      case Arg.scratch:
      case Arg.earlyDef:
        return false;
      default:
        throw new Error('Bad role');
    }
  }

  static isLateUse(role: string): boolean {
    switch (role) {
      case Arg.lateUse:
      case Arg.lateColdUse:
      case Arg.scratch:
        return true;
      case Arg.coldUse:
      case Arg.use:
      case Arg.useDef:
      case Arg.useZDef:
      case Arg.def:
      case Arg.zDef:
      case Arg.useAddr:
      case Arg.earlyDef:
        return false;
      default:
        throw new Error('Bad role');
    }
  }

  static isAnyDef(role: string): boolean {
    switch (role) {
      case Arg.use:
      case Arg.coldUse:
      case Arg.useAddr:
      case Arg.lateUse:
      case Arg.lateColdUse:
        return false;
      case Arg.def:
      case Arg.useDef:
      case Arg.zDef:
      case Arg.useZDef:
      case Arg.earlyDef:
      case Arg.scratch:
        return true;
      default:
        throw new Error('Bad role');
    }
  }

  static isEarlyDef(role: string): boolean {
    switch (role) {
      case Arg.use:
      case Arg.coldUse:
      case Arg.useAddr:
      case Arg.lateUse:
      case Arg.def:
      case Arg.useDef:
      case Arg.zDef:
      case Arg.useZDef:
      case Arg.lateColdUse:
        return false;
      case Arg.earlyDef:
      case Arg.scratch:
        return true;
      default:
        throw new Error('Bad role');
    }
  }

  static isLateDef(role: string): boolean {
    switch (role) {
      case Arg.use:
      case Arg.coldUse:
      case Arg.useAddr:
      case Arg.lateUse:
      case Arg.earlyDef:
      case Arg.scratch:
      case Arg.lateColdUse:
        return false;
      case Arg.def:
      case Arg.useDef:
      case Arg.zDef:
      case Arg.useZDef:
        return true;
      default:
        throw new Error('Bad role');
    }
  }

  static isZDef(role: string): boolean {
    switch (role) {
      case Arg.use:
      case Arg.coldUse:
      case Arg.useAddr:
      case Arg.lateUse:
      case Arg.def:
      case Arg.earlyDef:
      case Arg.scratch:
      case Arg.lateColdUse:
        return false;
      case Arg.zDef:
      case Arg.useZDef:
        return true;
      default:
        throw new Error('Bad role');
    }
  }

  static typeForB3Type(type: string): string {
    switch (type) {
      case nInt32:
      case nInt64:
        return GP;
      case nFloat:
      case dDouble:
        return FP;
      default:
        throw new Error('Bad B3 type');
    }
  }

  static widthForB3Type(type: string): number {
    switch (type) {
      case nInt32:
      case nInt64:
        return ARG_BYTES_NUM_32;
      case nFloat:
      case dDouble:
        return ARG_BYTES_NUM_64;
      default:
        throw new Error('Bad B3 type');
    }
  }

  static conservativeWidth(type: string): number {
    return type === GP ? PTR : ARG_BYTES_NUM_64;
  }

  static minimumWidth(type: string): number {
    return type === GP ? ARG_BYTES_NUM_8 : ARG_BYTES_NUM_32;
  }

  static bytes(width: number): number {
    return width / ARG_BYTES_NUM_8;
  }

  static widthForBytes(bytes: number): number {
    switch (bytes) {
      case 0:
      case 1:
        return ARG_BYTES_NUM_8;
      case ARG_BYTES_NUM_2:
        return ARG_BYTES_NUM_16;
      case ARG_BYTES_NUM_3:
      case ARG_BYTES_NUM_4:
        return ARG_BYTES_NUM_32;
      default:
        if (bytes > ARG_BYTES_NUM_8) {
          throw new Error('Bad number of bytes');
        }
        return ARG_BYTES_NUM_64;
    }
  }

  static createTmp(tmp: Reg): Arg {
    let result = new Arg();
    result._kind = Arg.tmp;
    result._tmp = tmp;
    return result;
  }

  static fromReg(reg: Reg): Arg {
    return Arg.createTmp(reg);
  }

  static createImm(value: number): Arg {
    let result = new Arg();
    result._kind = Arg.imm;
    result._value = value;
    return result;
  }

  static createBigImm(lowValue: number, highValue: number = 0): Arg {
    let result = new Arg();
    result._kind = Arg.bigImm;
    result._lowValue = lowValue;
    result._highValue = highValue;
    return result;
  }

  createBitImm(value: number): Arg {
    let result = new Arg();
    result._kind = Arg.bitImm;
    result._value = value;
    return result;
  }

  createBitImm64(lowValue: number, highValue: number = 0): Arg {
    let result = new Arg();
    result._kind = Arg.bitImm64;
    result._lowValue = lowValue;
    result._highValue = highValue;
    return result;
  }

  static createAddr(base: Reg, offset: number = 0): Arg {
    let result = new Arg();
    result._kind = Arg.addr;
    result._base = base;
    result._offset = offset;
    return result;
  }

  static createStack(slot: StackSlot, offset: number = 0): Arg {
    let result = new Arg();
    result._kind = Arg.stack;
    result._slot = slot;
    result._offset = offset;
    return result;
  }

  static createCallArg(offset: number): Arg {
    let result = new Arg();
    result._kind = Arg.callArg;
    result._offset = offset;
    return result;
  }

  static createStackAddr(offsetFromFP: number, frameSize: number, width: number): Arg {
    let result = Arg.createAddr(Reg.callFrameRegister, offsetFromFP);
    if (!result.isValidForm(width)) {
      result = Arg.createAddr(Reg.stackPointerRegister, offsetFromFP + frameSize);
    }
    return result;
  }

  isValidScale(scale: number, width: number): boolean {
    switch (scale) {
      case 1:
      case ARG_BYTES_NUM_2:
      case ARG_BYTES_NUM_4:
      case ARG_BYTES_NUM_8:
        return true;
      default:
        return false;
    }
  }

  logScale(scale: number): number {
    switch (scale) {
      case 1:
        return 0;
      case ARG_BYTES_NUM_2:
        return 1;
      case ARG_BYTES_NUM_4:
        return ARG_BYTES_NUM_2;
      case ARG_BYTES_NUM_8:
        return ARG_BYTES_NUM_3;
      default:
        throw new Error('Bad scale');
    }
  }

  static createIndex(base: Reg, index: Reg, scale: number = 1, offset: number = 0): Arg {
    let result = new Arg();
    result._kind = Arg.index;
    result._base = base;
    result._index = index;
    result._scale = scale;
    result._offset = offset;
    return result;
  }

  static createRelCond(condition: string): Arg {
    let result = new Arg();
    result._kind = Arg.relCond;
    result._condition = condition;
    return result;
  }

  static createResCond(condition: string): Arg {
    let result = new Arg();
    result._kind = Arg.resCond;
    result._condition = condition;
    return result;
  }

  static createDoubleCond(condition: string): Arg {
    let result = new Arg();
    result._kind = Arg.doubleCond;
    result._condition = condition;
    return result;
  }

  createWidth(width: number): Arg {
    let result = new Arg();
    result._kind = Arg.width;
    result._width = width;
    return result;
  }

  static createSpecial(): Arg {
    let result = new Arg();
    result._kind = Arg.special;
    return result;
  }

  get kind(): string {
    return this._kind;
  }

  isTmp(): boolean {
    return this._kind === Arg.tmp;
  }

  isImm(): boolean {
    return this._kind === Arg.imm;
  }

  isBigImm(): boolean {
    return this._kind === Arg.bigImm;
  }

  isBitImm(): boolean {
    return this._kind === Arg.bitImm;
  }

  isBitImm64(): boolean {
    return this._kind === Arg.bitImm64;
  }

  isSomeImm(): boolean {
    switch (this._kind) {
      case Arg.imm:
      case Arg.bitImm:
        return true;
      default:
        return false;
    }
  }

  isSomeBigImm(): boolean {
    switch (this._kind) {
      case Arg.bigImm:
      case Arg.bitImm64:
        return true;
      default:
        return false;
    }
  }

  isAddr(): boolean {
    return this._kind === Arg.addr;
  }

  get isStack(): boolean {
    return this._kind === Arg.stack;
  }

  get isCallArg(): boolean {
    return this._kind === Arg.callArg;
  }

  isIndex(): boolean {
    return this._kind === Arg.index;
  }

  isMemory(): boolean {
    switch (this._kind) {
      case Arg.addr:
      case Arg.stack:
      case Arg.callArg:
      case Arg.index:
        return true;
      default:
        return false;
    }
  }

  isStackMemory(): boolean {
    switch (this._kind) {
      case Arg.addr:
        return this._base === Reg.callFrameRegister || this._base === Reg.stackPointerRegister;
      case Arg.stack:
      case Arg.callArg:
        return true;
      default:
        return false;
    }
  }

  isRelCond(): boolean {
    return this._kind === Arg.relCond;
  }

  isResCond(): boolean {
    return this._kind === Arg.resCond;
  }

  isDoubleCond(): boolean {
    return this._kind === Arg.doubleCond;
  }

  isCondition(): boolean {
    switch (this._kind) {
      case Arg.relCond:
      case Arg.resCond:
      case Arg.doubleCond:
        return true;
      default:
        return false;
    }
  }

  isWidth(): boolean {
    return this._kind === Arg.width;
  }

  isSpecial(): boolean {
    return this._kind === Arg.special;
  }

  isAlive(): boolean {
    return this.isTmp() || this.isStack;
  }

  tmp(): Reg {
    if (this._kind !== Arg.tmp) {
      throw new Error('Called .tmp for non-tmp');
    }
    return this._tmp;
  }

  value(): number {
    if (!this.isSomeImm()) {
      throw new Error('Called .value for non-imm');
    }
    return this._value;
  }

  lowValue(): number {
    if (!this.isSomeBigImm()) {
      throw new Error('Called .lowValue for non-big-imm');
    }
    return this._lowValue;
  }

  highValue(): number {
    if (!this.isSomeBigImm()) {
      throw new Error('Called .highValue for non-big-imm');
    }
    return this._highValue;
  }

  base(): Reg | null {
    switch (this._kind) {
      case Arg.addr:
      case Arg.index:
        return this._base;
      default:
        throw new Error('Called .base for non-address');
    }
  }

  hasOffset(): boolean {
    return this.isMemory();
  }

  get offset(): number {
    switch (this._kind) {
      case Arg.addr:
      case Arg.index:
      case Arg.stack:
      case Arg.callArg:
        return this._offset;
      default:
        throw new Error('Called .offset for non-address');
    }
  }

  stackSlot(): StackSlot | null {
    if (this._kind !== Arg.stack) {
      throw new Error('Called .stackSlot for non-address');
    }
    return this._slot;
  }

  index(): Reg | null {
    if (this._kind !== Arg.index) {
      throw new Error('Called .index for non-Index');
    }
    return this._index;
  }

  scale(): number {
    if (this._kind !== Arg.index) {
      throw new Error('Called .scale for non-Index');
    }
    return this._scale;
  }

  width(): number {
    if (this._kind !== Arg.width) {
      throw new Error('Called .width for non-Width');
    }
    return this._width;
  }

  isGPTmp(): boolean {
    let istmp = this.isTmp();
    let isgp = this.tmp();
    return istmp && isgp!.isGP;
  }

  isFPTmp(): boolean {
    let isfp = this.tmp();
    return this.isTmp() && isfp!.isFP;
  }

  isGP(): boolean {
    switch (this._kind) {
      case Arg.imm:
      case Arg.bigImm:
      case Arg.bitImm:
      case Arg.bitImm64:
      case Arg.addr:
      case Arg.index:
      case Arg.stack:
      case Arg.callArg:
      case Arg.relCond:
      case Arg.resCond:
      case Arg.doubleCond:
      case Arg.width:
      case Arg.special:
        return true;
      case Arg.tmp:
        return this.isGPTmp();
      case Arg.invalid:
        return false;
      default:
        throw new Error('Bad kind');
    }
  }

  isFP(): boolean {
    switch (this._kind) {
      case Arg.imm:
      case Arg.bitImm:
      case Arg.bitImm64:
      case Arg.relCond:
      case Arg.resCond:
      case Arg.doubleCond:
      case Arg.width:
      case Arg.special:
      case Arg.invalid:
        return false;
      case Arg.addr:
      case Arg.index:
      case Arg.stack:
      case Arg.callArg:
      case Arg.bigImm:
        return true;
      case Arg.tmp:
        return this.isFPTmp();
      default:
        throw new Error('Bad kind');
    }
  }

  hasType(): boolean {
    switch (this._kind) {
      case Arg.imm:
      case Arg.bitImm:
      case Arg.bitImm64:
      case Arg.tmp:
        return true;
      default:
        return false;
    }
  }

  type(): string {
    return this.isGP() ? GP : FP;
  }

  isType(type: string): boolean {
    switch (type) {
      case GP:
        return this.isGP();
      case FP:
        return this.isFP();
      default:
        throw new Error('Bad type');
    }
  }

  isCompatibleType(other: Arg): boolean {
    if (this.hasType()) {
      return other.isType(this.type());
    }
    if (other.hasType()) {
      return this.isType(other.type());
    }
    return true;
  }

  isGPR(): boolean {
    let isGPR = this.tmp();
    return this.isTmp() && isGPR.isGPR;
  }

  gpr(): Tmp {
    let gpr = this.tmp();
    let tmp = gpr.gpr();
    return tmp as Tmp;
  }

  isFPR(): boolean {
    let gpr = this.tmp();
    return this.isTmp() && gpr!.isFPR;
  }

  fpr(): Tmp {
    let fpr = this.tmp();
    let tmp = fpr!.fpr();
    return tmp as Tmp;
  }

  isReg(): boolean {
    let isReg = this.tmp();
    return this.isTmp() && isReg!.isReg;
  }

  reg(): Reg {
    let reg = this.tmp();
    let tmp = reg!.reg();
    return tmp as Reg;
  }

  isValidImmForm(value: number): boolean {
    return isRepresentableAsInt32(value);
  }

  isValidBitImmForm(value: number): boolean {
    return isRepresentableAsInt32(value);
  }

  isValidBitImm64Form(value: number): boolean {
    return isRepresentableAsInt32(value);
  }

  isValidAddrForm(offset: number, width: number): boolean {
    return true;
  }

  isValidIndexForm(scale: number, offset: number, width: number): boolean {
    if (!this.isValidScale(scale, width)) {
      return false;
    }
    return true;
  }

  isValidForm(width: number): boolean {
    switch (this._kind) {
      case Arg.invalid:
        return false;
      case Arg.tmp:
        return true;
      case Arg.imm:
        return this.isValidImmForm(width);
      case Arg.bigImm:
        return true;
      case Arg.bitImm:
        return this.isValidBitImmForm(this._value);
      case Arg.bitImm64:
        return this.isValidBitImm64Form(this._value);
      case Arg.addr:
      case Arg.stack:
      case Arg.callArg:
        return this.isValidAddrForm(this._offset, width);
      case Arg.index:
        return this.isValidIndexForm(this._scale, this._offset, width);
      case Arg.relCond:
      case Arg.resCond:
      case Arg.doubleCond:
      case Arg.width:
      case Arg.special:
        return true;
      default:
        throw new Error('Bad kind');
    }
  }

  forEachTmpFast(fun: (reg: Reg) => Reg | null): Arg | null {
    switch (this._kind) {
      case Arg.tmp:
        let replacement = fun(this._tmp);
        if (replacement !== null) {
          return Arg.createTmp(replacement);
        }
        break;
      case Arg.addr:
        let replacementB = fun(this._base!);
        if (replacementB !== null) {
          return Arg.createAddr(replacementB, this._offset);
        }
        break;
      case Arg.index:
        let baseReplacement = fun(this._base) as Reg;
        let indexReplacement = fun(this._index) as Reg;
        if (baseReplacement !== null || indexReplacement !== null) {
          return Arg.createIndex(
            baseReplacement !== null ? baseReplacement : this._base,
            indexReplacement !== null ? indexReplacement : this._index,
            this._scale,
            this._offset
          );
        }
        break;
      default:
        break;
    }
    return null;
  }

  usesTmp(expectedTmp: Reg): boolean {
    let usesTmp = false;
    this.forEachTmpFast(tmp => {
      return null;
    });
    return usesTmp;
  }

  forEachTmp(role: string, type: string, width: number, fun: (arg: Reg, role: string, type: string, width: number) => Reg): Arg {
    switch (this._kind) {
      case Arg.tmp:
        let replacement = fun(this._tmp, role, type, width);
        if (replacement !== null) {
          return Arg.createTmp(replacement);
        }
        break;
      case Arg.addr:
        let replacementA = fun(this._base, Arg.use, GP, role === Arg.useAddr ? width : PTR);
        if (replacementA !== null) {
          return Arg.createAddr(replacementA, this._offset);
        }
        break;
      case Arg.index:
        let baseReplacement = fun(this._base!, Arg.use, GP, role === Arg.useAddr ? width : PTR);
        let indexReplacement = fun(this._index!, Arg.use, GP, role === Arg.useAddr ? width : PTR);
        if (baseReplacement !== null || indexReplacement !== null) {
          return Arg.createIndex((baseReplacement ?? this._base)!, (indexReplacement ?? this._index)!, this._scale, this._offset);
        }
        break;
      default:
        break;
    }
    return null;
  }

  is(thing: typeof StackSlot): StackSlot {
    return thing.extract(this);
  }

  as(thing: typeof StackSlot): StackSlot {
    return thing.extract(this);
  }

  forEach(
    thing: typeof StackSlot,
    role: string,
    type: string,
    width: number,
    fun: (value: StackSlot, role: string, type: string, width: number) => StackSlot): Arg {
    return thing.forEach(this, role, type, width, fun);
  }

  static extract(arg: Arg): Arg {
    return arg;
  }

  static forEachFast(arg: Arg, fun: (Arg) => Arg): Arg {
    return fun(arg);
  }

  static forEach(arg: Arg, role: string, type: string, width: number, fun: (arg: Arg, role: string, type: string, width: number) => Arg): Arg {
    return fun(arg, role, type, width);
  }

  condition(): string {
    switch (this._kind) {
      case Arg.relCond:
      case Arg.resCond:
      case Arg.doubleCond:
        return this._condition;
      default:
        throw new Error('Called .condition for non-condition');
    }
  }

  isInvertible(): boolean {
    switch (this._kind) {
      case Arg.relCond:
      case Arg.doubleCond:
        return true;
      case Arg.resCond:
        switch (this._condition) {
          case zero:
          case nonZero:
          case signed:
          case positiveOrZero:
            return true;
          default:
            return false;
        }
      default:
        return false;
    }
  }

  kindCode(kind: string): number {
    switch (kind) {
      case Arg.invalid:
        return 0;
      case Arg.tmp:
        return 1;
      case Arg.imm:
        return ARG_BYTES_NUM_2;
      case Arg.bigImm:
        return ARG_BYTES_NUM_3;
      case Arg.bitImm:
        return ARG_BYTES_NUM_4;
      case Arg.bitImm64:
        return ARG_BYTES_NUM_5;
      case Arg.addr:
        return ARG_BYTES_NUM_6;
      case Arg.stack:
        return ARG_BYTES_NUM_7;
      case Arg.callArg:
        return ARG_BYTES_NUM_8;
      case Arg.index:
        return ARG_BYTES_NUM_9;
      case Arg.relCond:
        return ARG_BYTES_NUM_10;
      case Arg.resCond:
        return ARG_BYTES_NUM_11;
      case Arg.doubleCond:
        return ARG_BYTES_NUM_12;
      case Arg.special:
        return ARG_BYTES_NUM_13;
      case Arg.widthArg:
        return ARG_BYTES_NUM_14;
      default:
        throw new Error('Bad kind');
    }
  }

  hash(): number {
    let result = this.kindCode(this._kind);
    switch (this._kind) {
      case Arg.invalid:
      case Arg.special:
        break;
      case Arg.tmp:
        result += this._tmp.hash();
        result = transBigInt32(result);
        result |= 0;
        break;
      case Arg.imm:
      case Arg.bitImm:
        result += this._value;
        result = transBigInt32(result);
        result |= 0;
        break;
      case Arg.bigImm:
      case Arg.bitImm64:
        result += this._lowValue;
        result = transBigInt32(result);
        result |= 0;
        result += this._highValue;
        result = transBigInt32(result);
        result |= 0;
        break;
      case Arg.callArg:
        result += this._offset;
        result = transBigInt32(result);
        result |= 0;
        break;
      case Arg.relCond:
        result += relCondCode(this._condition);
        result = transBigInt32(result);
        result |= 0;
        break;
      case Arg.resCond:
        result += resCondCode(this._condition);
        result = transBigInt32(result);
        result |= 0;
        break;
      case Arg.doubleCond:
        result += doubleCondCode(this._condition);
        result = transBigInt32(result);
        result |= 0;
        break;
      case Arg.widthArg:
        result += this._width;
        result = transBigInt32(result);
        result |= 0;
        break;
      case Arg.addr:
        result += this._offset;
        result = transBigInt32(result);
        result |= 0;
        result += this._base.hash();
        result = transBigInt32(result);
        result |= 0;
        break;
      case Arg.index:
        result += this._offset;
        result = transBigInt32(result);
        result |= 0;
        result += this._scale;
        result = transBigInt32(result);
        result |= 0;
        result += this._base.hash();
        result = transBigInt32(result);
        result |= 0;
        result += this._index.hash();
        result = transBigInt32(result);
        result |= 0;
        break;
      case Arg.stack:
        result += this._offset;
        result = transBigInt32(result);
        result |= 0;
        result += this.stackSlot().index;
        result = transBigInt32(result);
        result |= 0;
        break;
      default:
        break;
    }
    return result >>> 0;
  }

  toString(): string {
    switch (this._kind) {
      case Arg.invalid:
        return '<invalid>';
      case Arg.tmp:
        return this._tmp.toString();
      case Arg.imm:
        return '$' + String(this._value);
      case Arg.bigImm:
      case Arg.bitImm64:
        return '$0x(String(self._highValue, radix: 16))):(String(self._lowValue, radix: 16)))';
      case Arg.addr:
        return '(_offset ?? 0)';
      case Arg.index:
        return `${this._offset ?? 0}${this._index}${this._scale === 1 ? '' : this._scale}`;
      case Arg.stack:
        return '';
      case Arg.callArg:
        return '(_offset!)(callArg)';
      case Arg.relCond:
      case Arg.resCond:
      case Arg.doubleCond:
        return symbolName(this._condition);
      case Arg.special:
        return 'special';
      case Arg.width:
        return '(_value!)';
      default:
        throw new Error('Bad kind');
    }
  }
}

export function transBigInt32(bigInt32Num: number): number {
  let tmp = bigInt32Num;
  if (tmp > INT32_MAX) {
    let max = UINT32_MAX;
    tmp = tmp % (max + 1);
    if (tmp > INT32_MAX) {
      tmp = tmp - max - 1;
    }
  } else if (tmp < INT32_MIN) {
    let max = UINT32_MAX;
    tmp = tmp % (max + 1);
    if (tmp < INT32_MIN) {
      tmp = tmp + max + 1;
    }
  }
  return tmp;
}
